Comparison of DC hourly energy calculated using CFV measured Sandia coefficients provided in the mPERT data shows differences from measured values.
NREL collected data from mobile performance energy rating testbed (mPERT) stations in Eugene-OR, Cocoa-FL and Golden-CO for validating models for PV module performance (NREL/TP-5200-61610 April 2014).
The datasets are available from download from Bill Marion.
Download Link: Data For Validating Models.zip
%matplotlib inline
# imports
import numpy as np
from matplotlib import pylab
from matplotlib import pyplot as plt
import pandas as pd
import os
import seaborn as sns
# set figure size
sns.set_context('notebook', rc={'figure.figsize': (12, 8)})
# CONSTANTS
try:
DIRNAME = os.path.dirname(__file__)
except NameError:
DIRNAME = %pwd
MPERT = os.path.join(DIRNAME, 'mPERT')
PVMODULE = 'CdTe75669'
SITE = 'Golden'
CDTE_COCOA = os.path.join(MPERT, SITE, '%s_%s.csv' % (SITE, PVMODULE))
# `header=2` does not work, so use `skiprows=3` and use explicit `names`
# read the headers and names from the file
with open(CDTE_COCOA, 'r') as f:
for n, l in enumerate(f.readlines()):
if n == 0:
CDTE_COCOA_HEADERS = l.strip().split(',')
elif n == 1:
CDTE_COCOA_HEADERS = dict(zip(CDTE_COCOA_HEADERS, l.strip().split(',')))
else:
ALLNAMES = l.strip().split(',')
break
NAMES = [
'Time Stamp (local standard time) yyyy-mm-ddThh:mm:ss',
'POA irradiance CMP22 pyranometer (W/m2)',
'PV module back surface temperature (degC)',
'Isc (A)', 'Pmp (W)', 'Imp (A)', 'Vmp (V)', 'Voc (V)', 'FF (%FF)',
'Dry bulb temperature (degC)',
'Relative humidity (%RH)',
'Atmospheric pressure (mb)',
'Precipitation (mm) accumulated daily total',
'Direct normal irradiance (W/m2)',
'Global horizontal irradiance (W/m2)',
'Diffuse horizontal irradiance (W/m2)',
'PV module soiling derate'
]
# USECOLS = [0] + range(1, 15, 2) + range(20, 26, 2) + range(27, 34, 3) + [37]
USECOLS = [ALLNAMES.index(n) for n in NAMES]
# DTYPE = dict.fromkeys(NAMES[1:], np.float64)
# float is inferred, not necessary to pass `dtype=DTYPE`
# read CdTe from Cocoa
cdte_cocoa_raw = pd.read_csv(
CDTE_COCOA, skiprows=3, names=NAMES, usecols=USECOLS, index_col=0, parse_dates=True, na_values='-9999'
)
cdte_cocoa = cdte_cocoa_raw.dropna()
cdte_cocoa.head()
POA irradiance CMP22 pyranometer (W/m2) | PV module back surface temperature (degC) | Isc (A) | Pmp (W) | Imp (A) | Vmp (V) | Voc (V) | FF (%FF) | Dry bulb temperature (degC) | Relative humidity (%RH) | Atmospheric pressure (mb) | Precipitation (mm) accumulated daily total | Direct normal irradiance (W/m2) | Global horizontal irradiance (W/m2) | Diffuse horizontal irradiance (W/m2) | PV module soiling derate | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Time Stamp (local standard time) yyyy-mm-ddThh:mm:ss | ||||||||||||||||
2012-08-14 06:15:53 | 73.1 | 16.5 | 0.0777 | 3.6114 | 0.0602 | 60.0025 | 79.6054 | 58.38 | 17.9 | 51.7 | 823.0 | 0.0 | 268.1 | 118.3 | 67.0 | 0.994 |
2012-08-14 06:30:53 | 107.6 | 17.9 | 0.1057 | 5.0751 | 0.0817 | 62.0884 | 80.9840 | 59.30 | 19.3 | 50.2 | 823.4 | 0.0 | 308.9 | 153.6 | 82.2 | 0.994 |
2012-08-14 06:45:53 | 149.6 | 20.0 | 0.1427 | 7.0318 | 0.1128 | 62.3377 | 81.9221 | 60.16 | 20.4 | 46.4 | 823.6 | 0.0 | 358.5 | 197.6 | 95.9 | 0.994 |
2012-08-14 07:00:53 | 204.3 | 23.1 | 0.1968 | 9.9374 | 0.1574 | 63.1402 | 82.6190 | 61.12 | 22.2 | 38.1 | 823.3 | 0.0 | 437.9 | 256.7 | 110.4 | 0.994 |
2012-08-14 07:15:53 | 260.4 | 26.2 | 0.2591 | 13.6070 | 0.2137 | 63.6610 | 83.0083 | 63.27 | 22.6 | 37.8 | 822.9 | 0.0 | 491.8 | 308.3 | 123.3 | 0.994 |
cdte_cocoa.tail()
POA irradiance CMP22 pyranometer (W/m2) | PV module back surface temperature (degC) | Isc (A) | Pmp (W) | Imp (A) | Vmp (V) | Voc (V) | FF (%FF) | Dry bulb temperature (degC) | Relative humidity (%RH) | Atmospheric pressure (mb) | Precipitation (mm) accumulated daily total | Direct normal irradiance (W/m2) | Global horizontal irradiance (W/m2) | Diffuse horizontal irradiance (W/m2) | PV module soiling derate | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Time Stamp (local standard time) yyyy-mm-ddThh:mm:ss | ||||||||||||||||
2013-09-24 16:15:55 | 374.6 | 37.0 | 0.4003 | 21.9303 | 0.3415 | 64.2190 | 81.8386 | 66.95 | 27.3 | 22.6 | 819.4 | 0.0 | 785.8 | 266.5 | 44.3 | 1.0 |
2013-09-24 16:30:55 | 305.8 | 33.2 | 0.3156 | 17.2070 | 0.2663 | 64.6051 | 81.8459 | 66.62 | 25.6 | 23.0 | 819.4 | 0.0 | 741.2 | 214.9 | 41.3 | 1.0 |
2013-09-24 16:45:55 | 236.6 | 30.6 | 0.2322 | 12.3915 | 0.1929 | 64.2337 | 81.2157 | 65.70 | 25.0 | 23.2 | 819.5 | 0.0 | 681.3 | 163.2 | 36.8 | 1.0 |
2013-09-24 17:00:56 | 169.7 | 27.2 | 0.1549 | 7.8791 | 0.1252 | 62.9087 | 80.2694 | 63.38 | 24.1 | 24.0 | 819.4 | 0.0 | 604.6 | 112.1 | 31.4 | 1.0 |
2013-09-24 17:15:55 | 105.9 | 24.3 | 0.0879 | 3.8601 | 0.0682 | 56.5999 | 77.1912 | 56.91 | 23.1 | 27.1 | 819.1 | 0.0 | -1.7 | 15.4 | 25.4 | 1.0 |
CDTE_COCOA_HEADERS
{'City': 'Golden', 'Elevation meters above sea level': '1798.0', 'Latitude degrees N+': '39.74', 'Longitude degrees W-': '-105.18', 'PV module azimuth degrees': '180.0', 'PV module identifier': 'CdTe75669', 'PV module tilt degrees': '40.0', 'State': 'Colorado', 'Time Zone': '-7'}
press = cdte_cocoa['Atmospheric pressure (mb)']
ax_press = press.plot(title='weather', linestyle=':', marker='.')
ax_press.set_ylabel('press [mb]')
<matplotlib.text.Text at 0x20bc8710>
dry_temp = cdte_cocoa['Dry bulb temperature (degC)']
ax_temp = dry_temp.plot(title='weather', linestyle=':', marker='.')
ax_temp.set_ylabel('temp [degC]')
<matplotlib.text.Text at 0x21c335c0>
The SAPM coefficients were measured by CFV.
Documentation Link: Photovoltaic Array Performance Model SAND2004-3535 (Dec 2004)
SAPM_COEFF = os.path.join(MPERT, 'SandiaModelCoefficients.xlsx')
sapm_coeff = pd.read_excel(SAPM_COEFF, index_col=0)
sapm_coeff.head()
Vintage | Module Area [m^2] | Material | Series Cells | Parallel C-S | Isco | Voco | Impo | Vmpo | aIsc | ... | fd | a | b | C4 | C5 | Ixo | Ixxo | C6 | C7 | Data Source | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Model | |||||||||||||||||||||
aSiMicro03036 | NaN | 1.42 | NaN | 180 | 1 | 0.806778 | 230.4380 | 0.668410 | 169.8890 | 0.001096 | ... | 1 | -3.495404 | -0.128815 | 0.971026 | 0.028974 | 0.760934 | 0.474057 | 1.04883 | -0.048835 | 1 |
aSiMicro03038 | NaN | 1.42 | NaN | 180 | 1 | 0.768290 | 223.6900 | 0.624523 | 162.4460 | 0.001096 | ... | 1 | -3.566608 | -0.082174 | 1.008280 | -0.008281 | 0.724587 | 0.437334 | 1.06633 | -0.066335 | 1 |
aSiTandem72-46 | NaN | 0.79 | NaN | 38 | 1 | 1.105940 | 59.9138 | 0.887437 | 44.5723 | 0.000913 | ... | 1 | -3.515526 | -0.127401 | 0.987440 | 0.012560 | 1.031810 | 0.626962 | 1.07179 | -0.071793 | 1 |
aSiTandem90-31 | NaN | 0.79 | NaN | 38 | 1 | 1.113620 | 60.2213 | 0.864840 | 44.2755 | 0.000913 | ... | 1 | -3.378668 | -0.089963 | 0.980486 | 0.019514 | 1.017130 | 0.604894 | 1.05002 | -0.050023 | 1 |
aSiTriple28324 | NaN | 1.01 | NaN | 11 | 2 | 4.621040 | 23.0919 | 3.721990 | 16.5439 | 0.000981 | ... | 1 | -3.413780 | -0.138029 | 1.031970 | -0.031971 | 4.295040 | 2.519540 | 1.14692 | -0.146917 | 1 |
5 rows × 42 columns
PVLIB is software for modeling PV module and array performance. It is distributed freely by Sandia National Laboratories.
Documentation Link: PVLIB-Python RTFD
PVLIB contains the SAPM for calculating dc energy as a function of effective irradiance ($E_e$) and cell temperature. The effective irradiance depends on direct and diffuse irradiance and spectral and angle of incidence (AOI) modifiers. The spectral and angle of incidence modifiers in the SAPM are polynomial funcitons airmass (AM) and AOI respectively.
Documentation Links:
pvlib.pvsystem.sapm(eff_irrad, cell_temp, module)
pvlib.pvsystem.sapm_effective_irradiance
pvlib.pvsystem.sapm_aoi_loss
pvlib.pvsystem.sapm_spectral_loss
pvlib.irradiance.total_irrad
We will need the solar position and air mass to calculate the angle of incidence or projection of solar vector on the modules. PVLIB privides methods to calculate both. There is also a method from NREL called SOLPOS.
Since the mPERT dataset contains DNI, DHI, GHI and IPOA there are several options for calculating effective irradiance.
Filter the measured IPOA to limit f1 to AM near 1.5 and f2 to AOI < 40 so the effect of spectrum and incidence angle are minimized.
Decompose the measured GHI to predict DNI and DHI, then transpose the DHI and finally sum the projected DNI with the transposed DHI and any ground reflection
Projecting the measured DNI on the plane of array, and combine with transposed DHI and ground reflection.
The diffuse component must be transposed to the orientation of the module surface to account for the reduced view-factor of the diffuse skydome, and effects of horizon brightening, ground reflection and circumsolar irradiance.
from solar_utils import *
timestamps = cdte_cocoa.index
latitude = np.float64(CDTE_COCOA_HEADERS['Latitude degrees N+'])
longitude = np.float64(CDTE_COCOA_HEADERS['Longitude degrees W-'])
timezone = np.float64(CDTE_COCOA_HEADERS['Time Zone'])
# solar position and airmass
location = [latitude, longitude, timezone]
solar_position = np.array([
solposAM(location, d.timetuple()[:6], [p, t]) for d, p, t in zip(timestamps, press, dry_temp)
]).reshape((-1,4))
solpos_am_data = pd.DataFrame(solar_position, index=timestamps, columns=['ze','az','am','amp'])
solpos_am_data.head()
ze | az | am | amp | |
---|---|---|---|---|
Time Stamp (local standard time) yyyy-mm-ddThh:mm:ss | ||||
2012-08-14 06:15:53 | 78.956123 | 80.674080 | 5.091515 | 4.136542 |
2012-08-14 06:30:53 | 76.117218 | 82.984970 | 4.103316 | 3.335312 |
2012-08-14 06:45:53 | 73.259422 | 85.305954 | 3.435638 | 2.793279 |
2012-08-14 07:00:53 | 70.389336 | 87.648079 | 2.957519 | 2.403677 |
2012-08-14 07:15:53 | 67.513474 | 90.026833 | 2.600355 | 2.112371 |
from pvlib.solarposition import get_solarposition
import pytz
# use Etc/GMT instead of eastern time to avoid daylight savings - eastern will shift by 1-hour twice a year
eastern_gmt = pytz.timezone('Etc/GMT+%d' % -timezone)
elev = np.float64(CDTE_COCOA_HEADERS['Elevation meters above sea level'])
local_time = timestamps.tz_localize(eastern_gmt)
press_pa = press * 100 # convert pressure to Pa
spa = get_solarposition(time=local_time, latitude=latitude, longitude=longitude,
altitude=elev, pressure=press_pa, temperature=dry_temp)
spa.head()
apparent_elevation | apparent_zenith | azimuth | elevation | equation_of_time | zenith | |
---|---|---|---|---|---|---|
Time Stamp (local standard time) yyyy-mm-ddThh:mm:ss | ||||||
2012-08-14 06:15:53-07:00 | 11.043863 | 78.956137 | 80.676983 | 10.978320 | -4.569161 | 79.021680 |
2012-08-14 06:30:53-07:00 | 13.883205 | 76.116795 | 82.987660 | 13.830849 | -4.567154 | 76.169151 |
2012-08-14 06:45:53-07:00 | 16.740892 | 73.259108 | 85.308263 | 16.697616 | -4.565146 | 73.302384 |
2012-08-14 07:00:53-07:00 | 19.610500 | 70.389500 | 87.651167 | 19.573983 | -4.563137 | 70.426017 |
2012-08-14 07:15:53-07:00 | 22.486711 | 67.513289 | 90.029716 | 22.455210 | -4.561127 | 67.544790 |
ax1_sp = plt.subplot(2, 1, 1)
plt.plot(timestamps, 100 * (1 - solpos_am_data['ze'].values / spa['apparent_zenith'].values), linestyle=':', marker='.')
ax1_sp.set_xlabel('')
ax1_sp.set_xticklabels([])
ax1_sp.set_title('comparison of solpos with pvlib spa-python')
ax1_sp.set_ylabel('zenith [%]')
ax2_sp = plt.subplot(2, 1, 2)
plt.plot(timestamps, 100 * (1 - solpos_am_data['az'].values / spa['azimuth'].values), linestyle=':', marker='.')
ax2_sp.set_ylabel('azimuth [%]')
<matplotlib.text.Text at 0x2219e518>
import calendar
# pivot table to make 8760 X 24 plot of daily-hourly solar angles
solpos_am_hour = solpos_am_data.resample('H').mean() # resample hourly
year_offset = xrange(solpos_am_hour.index.year[0] - 1, solpos_am_hour.index.year[-1])
year_offset = [366 if calendar.isleap(y) else 365 for y in year_offset]
year_offset = np.cumsum(year_offset) - year_offset[0]
year_offset = np.array([year_offset[y - solpos_am_hour.index.year[0]] for y in solpos_am_hour.index.year])
solpos_am_hour.insert(0,'year_offset', year_offset)
solpos_am_hour.insert(0,'doy', solpos_am_hour.index.dayofyear + year_offset)
solpos_am_hour.insert(0,'hour', solpos_am_hour.index.hour)
solpos_am_hour
solpos_pivot = solpos_am_hour.pivot('hour', 'doy')[['ze', 'az']]
solpos_am_hour.head()
hour | doy | year_offset | ze | az | am | amp | |
---|---|---|---|---|---|---|---|
Time Stamp (local standard time) yyyy-mm-ddThh:mm:ss | |||||||
2012-08-14 06:00:00 | 6 | 227 | 0 | 76.110924 | 82.988335 | 4.210156 | 3.421711 |
2012-08-14 07:00:00 | 7 | 227 | 0 | 66.076401 | 91.269623 | 2.497406 | 2.029211 |
2012-08-14 08:00:00 | 8 | 227 | 0 | 54.660049 | 101.692871 | 1.738433 | 1.412586 |
2012-08-14 09:00:00 | 9 | 227 | 0 | 43.736076 | 114.501785 | 1.387654 | 1.126982 |
2012-08-14 10:00:00 | 10 | 227 | 0 | 34.122665 | 132.114929 | 1.209120 | 0.981326 |
solpos_am_hour.tail()
hour | doy | year_offset | ze | az | am | amp | |
---|---|---|---|---|---|---|---|
Time Stamp (local standard time) yyyy-mm-ddThh:mm:ss | |||||||
2013-09-24 13:00:00 | 13 | 633 | 366 | 45.637951 | 212.605652 | 1.430584 | 1.159706 |
2013-09-24 14:00:00 | 14 | 633 | 366 | 53.234955 | 229.703491 | 1.674788 | 1.356219 |
2013-09-24 15:00:00 | 15 | 633 | 366 | 62.845985 | 243.337738 | 2.207033 | 1.786041 |
2013-09-24 16:00:00 | 16 | 633 | 366 | 73.585175 | 254.642868 | 3.623126 | 2.930944 |
2013-09-24 17:00:00 | 17 | 633 | 366 | 82.005142 | 262.258606 | 7.035587 | 5.689769 |
# plot zeniths
ze_doy_hour = solpos_pivot['ze']
ax_ze = ze_doy_hour.plot(legend=None, title='solar position')
ax_ze.set_ylabel('zenith [degrees]')
<matplotlib.text.Text at 0x22e78e10>
# plot azimuths
az_doy_hour = solpos_pivot['az']
ax_az = az_doy_hour.plot(legend=None, title='solar position')
ax_az.set_ylabel('azimuth [degrees]')
<matplotlib.text.Text at 0x21734eb8>
Air mass is needed for the spectral response of pv device to incident irradiance, and the spectral mismatch between the pv-device and the response of sensors used to measure irradiance. However, the response of thermopile radiometer is fairly flat out to 2.8 $\mu m$ compared to Si response, so the device mismatch can be ignored.
Both SOLPOS and PVLIB can calculate airmass and pressure corrected air mass. There is close agreement between the two methods as demonstrated below.
from pvlib.atmosphere import relativeairmass, absoluteairmass
# calculate airmass using PVLIB
ram = relativeairmass(solpos_am_data['ze']) # relative airmass as a function of solar zenith only
press_pa = press * 100 # convert pressure from mB to Pa
aam = absoluteairmass(ram, 100 * press) # absolute (or pressure corrected airmass)
# make a dataframe with existing airmass calculated from SOLPOS
am = pd.concat([solpos_am_data[['am', 'amp']], ram, aam], axis=1)
am.columns = ['am', 'amp', 'ram', 'aam'] # rename columns for convenience
# make comparison plots
ax1_am = plt.subplot(2, 1, 1)
(1 - am['2013-01-21':'2013-01-28']['ram'] / am['2013-01-21':'2013-01-28']['am']).plot(ax=ax1_am, linestyle=':', marker='o')
ax1_am.set_xlabel('')
ax1_am.set_xticklabels([])
ax1_am.set_ylabel('relative')
ax2_am = plt.subplot(2, 1, 2)
(1 - am['2013-01-21':'2013-01-28']['aam'] / am['2013-01-21':'2013-01-28']['amp']).plot(ax=ax2_am, linestyle=':', marker='o')
ax2_am.set_ylabel('absolute (press. corr.)')
<matplotlib.text.Text at 0x24694cc0>
The Hay-Davies and Reindl models divides diffuse into components on tilted surface. Reindl extends Hay-Davies by adding a term for horizon brightening. None of the transposition models include ground reflection which is handled separately.
PVPMC model description:
from pvlib.irradiance import reindl, extraradiation, grounddiffuse, globalinplane, aoi as calc_aoi
surface_azimuth = np.float64(CDTE_COCOA_HEADERS['PV module azimuth degrees'])
surface_tilt = np.float64(CDTE_COCOA_HEADERS['PV module tilt degrees'])
dni = cdte_cocoa['Direct normal irradiance (W/m2)']
dhi = cdte_cocoa['Diffuse horizontal irradiance (W/m2)']
ghi = cdte_cocoa['Global horizontal irradiance (W/m2)']
solar_zenith = solpos_am_data['ze']
solar_azimuth = solpos_am_data['az']
extra = extraradiation(timestamps) # extra terrestrial radiation [W/m^2]
sky_diffuse = reindl(surface_tilt, surface_azimuth, dhi, dni, ghi, extra, solar_zenith, solar_azimuth)
sky_diffuse.head()
Time Stamp (local standard time) yyyy-mm-ddThh:mm:ss 2012-08-14 06:15:53 51.626648 2012-08-14 06:30:53 65.842477 2012-08-14 06:45:53 78.932439 2012-08-14 07:00:53 92.527953 2012-08-14 07:15:53 105.725621 dtype: float64
irrad = pd.concat([dni, dhi, ghi, sky_diffuse, extra], axis=1)
irrad.columns = ['dni', 'dhi', 'ghi', 'sky_diffuse', 'extra']
ax_diff = irrad['2013-1-21':'2013-01-28'][['sky_diffuse', 'dhi']].plot(title='week 1 diffuse')
ax_diff.set_ylabel('diffuse irradiance [$W/m^2$]')
<matplotlib.text.Text at 0x24ff9ac8>
ground_diff = grounddiffuse(surface_tilt, ghi)
ground_diff.head()
Time Stamp (local standard time) yyyy-mm-ddThh:mm:ss 2012-08-14 06:15:53 3.459618 2012-08-14 06:30:53 4.491947 2012-08-14 06:45:53 5.778702 2012-08-14 07:00:53 7.507049 2012-08-14 07:15:53 9.016062 Name: diffuse_ground, dtype: float64
aoi = calc_aoi(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth)
poa = globalinplane(aoi, dni, sky_diffuse, ground_diff)
poa.head()
poa_global | poa_direct | poa_diffuse | |
---|---|---|---|
Time Stamp (local standard time) yyyy-mm-ddThh:mm:ss | |||
2012-08-14 06:15:53 | 67.019215 | 11.932949 | 55.086266 |
2012-08-14 06:30:53 | 103.569448 | 33.235024 | 70.334423 |
2012-08-14 06:45:53 | 145.755632 | 61.044491 | 84.711141 |
2012-08-14 07:00:53 | 201.740392 | 101.705390 | 100.035002 |
2012-08-14 07:15:53 | 258.969109 | 144.227427 | 114.741683 |
ax_poa = poa['2013-01-21':'2013-01-28'].plot(title='week 1 plane of array')
ax_poa.set_ylabel('plane of array irradiance [$W/m^2$]')
<matplotlib.text.Text at 0x252c6c50>
ax_irrad = irrad['2013-01-21':'2013-01-28'][['ghi', 'dni', 'dhi']].plot(title='week 1 irradiance')
ax_irrad.set_ylabel('plane of array irradiance [$W/m^2$]')
<matplotlib.text.Text at 0x253ade80>
ipoa = cdte_cocoa['POA irradiance CMP22 pyranometer (W/m2)']
ax_meas = ipoa['2013-01-21':'2013-01-28'].plot(title='measured plane of array')
ax_meas.set_ylabel('plane of array irradiance [$W/m^2$]')
<matplotlib.text.Text at 0x25374358>
poa_pred_meas = pd.concat([poa['poa_global'], ipoa], axis=1)
poa_pred_meas.columns = ['pred', 'meas']
ax_pred_meas = poa_pred_meas['2013-01-21':'2013-01-28'][['pred', 'meas']].plot(title='week 1 predicted vs. measured')
ax_pred_meas.set_ylabel('plane of array global irradiance [$W/m^2$]')
<matplotlib.text.Text at 0x25ca0080>
A module in PVLIB is a dictionary like object that has the SAPM coefficients. A Pandas DataFrame
works for this, but PVLIB uses different names for some of the SAPM module parameters, so we have to rename them.
# rename some parameters for PVLIB
sapm_coeff_renamed = sapm_coeff.rename(columns={
'aIsc': 'Aisc', 'aImp': 'Aimp', 'BVoco': 'Bvoco', 'BVmpo': 'Bvmpo', 'mBVoc': 'Mbvoc', 'mBVmp': 'Mbvmp', 'n': 'N',
'Series Cells': 'Cells_in_Series', 'Ixo': 'IXO', 'Ixxo': 'IXXO', 'fd': 'FD'
})
pvmodule = sapm_coeff_renamed.loc[PVMODULE]
pvmodule
Vintage NaN Module Area [m^2] 7.200000e-01 Material NaN Cells_in_Series 1.160000e+02 Parallel C-S 1.000000e+00 Isco 1.144860e+00 Voco 8.915270e+01 Impo 9.738220e-01 Vmpo 6.486870e+01 Aisc 5.100000e-04 Aimp 2.970000e-04 C0 1.004510e+00 C1 -4.508150e-03 Bvoco -2.144840e-01 Mbvoc 0.000000e+00 Bvmpo -1.666920e-01 Mbvmp 0.000000e+00 N 1.375500e+00 C2 -8.583190e-01 C3 -1.658730e+01 A0 9.875896e-01 A1 1.467286e-02 A2 -3.924308e-03 A3 9.745336e-05 A4 -2.810669e-06 B0 1.000000e+00 B1 -3.159504e-03 B2 4.144566e-04 B3 -1.740940e-05 B4 2.870862e-07 B5 -1.748383e-09 d(Tc) 3.000000e+00 FD 1.000000e+00 a -3.262124e+00 b -9.152990e-02 C4 1.002430e+00 C5 -2.427440e-03 IXO 1.102710e+00 IXXO 6.519600e-01 C6 1.120490e+00 C7 -1.204920e-01 Data Source 1.000000e+00 Name: CdTe75669, dtype: float64
The SAPM uses an input called effective irradiance that accounts for angular and spectral response of the solar device to incident radiation.
from pvlib.pvsystem import sapm, sapm_effective_irradiance
eff = sapm_effective_irradiance(poa['poa_direct'], poa['poa_diffuse'], solpos_am_data['amp'], aoi, pvmodule)
eff.head()
Time Stamp (local standard time) yyyy-mm-ddThh:mm:ss 2012-08-14 06:15:53 0.055541 2012-08-14 06:30:53 0.081040 2012-08-14 06:45:53 0.115708 2012-08-14 07:00:53 0.165268 2012-08-14 07:15:53 0.221157 dtype: float64
We can derive this from measured module temperature.
$$ T_c = T_m + \frac{E}{E_0} \Delta T $$E0 = 1000.0 # [W/m**2]
def calc_celltemp(ipoa, tmod, dtc, e0=E0):
"""
Cell temperature using SAPM correlation with irradiance.
Parameters:
ipoa (float): measured solar irradiance [W/m**2]
tmod (float): module temperature [C]
dtc (float): temperature difference coefficient [C]
e0 (float): referencec irradiance [W/m**2]
Returns:
float: cell temperature
"""
return tmod + ipoa / e0 * dtc
modtemp = cdte_cocoa['PV module back surface temperature (degC)']
dtc = pvmodule['d(Tc)']
celltemp = calc_celltemp(poa['poa_global'], modtemp, dtc)
celltemp.head()
Time Stamp (local standard time) yyyy-mm-ddThh:mm:ss 2012-08-14 06:15:53 16.701058 2012-08-14 06:30:53 18.210708 2012-08-14 06:45:53 20.437267 2012-08-14 07:00:53 23.705221 2012-08-14 07:15:53 26.976907 dtype: float64
soiling = cdte_cocoa['PV module soiling derate']
soiling.head()
Time Stamp (local standard time) yyyy-mm-ddThh:mm:ss 2012-08-14 06:15:53 0.994 2012-08-14 06:30:53 0.994 2012-08-14 06:45:53 0.994 2012-08-14 07:00:53 0.994 2012-08-14 07:15:53 0.994 Name: PV module soiling derate, dtype: float64
results = sapm(eff, celltemp, pvmodule)
results.head()
i_sc | i_mp | v_oc | v_mp | p_mp | i_x | i_xx | |
---|---|---|---|---|---|---|---|
Time Stamp (local standard time) yyyy-mm-ddThh:mm:ss | |||||||
2012-08-14 06:15:53 | 0.063317 | 0.054183 | 79.412430 | 57.162482 | 3.097260 | 0.061126 | 0.040160 |
2012-08-14 06:30:53 | 0.092459 | 0.079086 | 80.542261 | 60.150208 | 4.757052 | 0.089253 | 0.058482 |
2012-08-14 06:45:53 | 0.132162 | 0.112975 | 81.425358 | 62.263690 | 7.034268 | 0.127569 | 0.083281 |
2012-08-14 07:00:53 | 0.189084 | 0.161486 | 82.082635 | 63.671050 | 10.281968 | 0.182492 | 0.118507 |
2012-08-14 07:15:53 | 0.253449 | 0.216251 | 82.502030 | 64.339570 | 13.913478 | 0.244580 | 0.157875 |
# pivot table to make 8760 X 24 plot of daily-hourly power
p_mp_w_soiling = results['p_mp'] * soiling
power = pd.concat([p_mp_w_soiling, cdte_cocoa['Pmp (W)']], axis=1).rename(columns={0:'Pcalc', 'Pmp (W)':'Pmeas'})
power.head()
Pcalc | Pmeas | |
---|---|---|
Time Stamp (local standard time) yyyy-mm-ddThh:mm:ss | ||
2012-08-14 06:15:53 | 3.078677 | 3.6114 |
2012-08-14 06:30:53 | 4.728509 | 5.0751 |
2012-08-14 06:45:53 | 6.992063 | 7.0318 |
2012-08-14 07:00:53 | 10.220277 | 9.9374 |
2012-08-14 07:15:53 | 13.829997 | 13.6070 |
power_normalized = power.resample('5T').mean()
power_day = power_normalized.groupby(pd.TimeGrouper("D")).sum().dropna() * 5.0 / 60.0
power_day.head()
Pcalc | Pmeas | |
---|---|---|
Time Stamp (local standard time) yyyy-mm-ddThh:mm:ss | ||
2012-08-14 | 120.735176 | 123.925950 |
2012-08-15 | 87.783359 | 87.581933 |
2012-08-16 | 85.742462 | 88.058517 |
2012-08-17 | 130.691761 | 133.286367 |
2012-08-18 | 144.485027 | 150.599150 |
bias_yr = 100 * (1 - power_day['Pcalc'] / power_day['Pmeas'])
mbe = bias_yr.mean()
rmse = np.sqrt(np.mean(bias_yr**2))
mbe_yr = mbe * np.ones([timestamps.size, 1])
mbe_rmse_yr = np.concatenate([mbe_yr, mbe_yr + rmse, mbe_yr - rmse], axis=1)
ax_p_yr = bias_yr.plot(title='measured vs predicted', marker='.', linestyle=':')
ax_p_yr.set_ylabel('bias [%]')
ax_p_yr.plot(timestamps, mbe_rmse_yr, 'r--', linewidth=2)
[<matplotlib.lines.Line2D at 0x25e64048>, <matplotlib.lines.Line2D at 0x21714cc0>, <matplotlib.lines.Line2D at 0x2158bf28>]
print 'Daily MBE = %g [%%], RMSE = %g [%%] for entire dataset (approx. one year)' % (mbe, rmse)
Daily MBE = 2.12774 [%], RMSE = 3.8474 [%] for entire dataset (approx. one year)
power_hour = power.resample('H').mean() # resample hourly
year_offset = xrange(power_hour.index.year[0] - 1, power_hour.index.year[-1])
year_offset = [366 if calendar.isleap(y) else 365 for y in year_offset]
year_offset = np.cumsum(year_offset) - year_offset[0]
year_offset = np.array([year_offset[y - power_hour.index.year[0]] for y in power_hour.index.year])
power_hour.insert(0,'doy', power_hour.index.dayofyear + year_offset)
power_hour.insert(0,'hour', power_hour.index.hour)
power_hour.head()
hour | doy | Pcalc | Pmeas | |
---|---|---|---|---|
Time Stamp (local standard time) yyyy-mm-ddThh:mm:ss | ||||
2012-08-14 06:00:00 | 6 | 227 | 4.933083 | 5.239433 |
2012-08-14 07:00:00 | 7 | 227 | 15.707541 | 15.792925 |
2012-08-14 08:00:00 | 8 | 227 | 30.217826 | 31.014900 |
2012-08-14 09:00:00 | 9 | 227 | 42.587314 | 43.961825 |
2012-08-14 10:00:00 | 10 | 227 | 52.089224 | 53.533350 |
# plot azimuths
p_doy_hour = power_hour.dropna().pivot('hour', 'doy')
p_doy_hour['Pcalc'].head()
doy | 227 | 228 | 229 | 230 | 231 | 233 | 234 | 237 | 238 | 239 | ... | 615 | 616 | 619 | 620 | 623 | 626 | 628 | 630 | 632 | 633 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
hour | |||||||||||||||||||||
5 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
6 | 4.933083 | 5.220336 | 4.718536 | 4.962233 | 4.761729 | 3.572900 | 4.696720 | 8.015841 | 4.630857 | 4.434917 | ... | 4.242676 | 4.745489 | NaN | NaN | 4.640460 | 4.766579 | 5.815551 | 4.849049 | NaN | 5.698496 |
7 | 15.707541 | 13.717901 | 12.831400 | 16.026760 | 16.224313 | 11.981910 | 16.147615 | 16.442928 | 8.623095 | 17.574586 | ... | 17.593583 | 17.943199 | NaN | NaN | 13.074516 | 19.553165 | 14.563093 | 19.906450 | 9.667065 | 21.353455 |
8 | 30.217826 | 26.777238 | 25.481729 | 29.583504 | 32.236909 | 18.670610 | 31.280512 | 33.882799 | 12.323609 | 33.625342 | ... | 33.633224 | 34.104475 | 3.520742 | 3.377240 | 41.566468 | 36.708575 | 34.268826 | 36.694247 | 16.356363 | 37.928147 |
9 | 42.587314 | 38.998897 | 38.878610 | 40.830905 | 45.412573 | 29.033015 | 43.749099 | 39.629611 | 22.544707 | 46.414684 | ... | 47.057236 | 47.118323 | 3.826749 | 9.545445 | 43.962348 | 49.607067 | 17.953721 | 49.870907 | 44.339394 | NaN |
5 rows × 241 columns
p_doy_hour['Pmeas'].head()
doy | 227 | 228 | 229 | 230 | 231 | 233 | 234 | 237 | 238 | 239 | ... | 615 | 616 | 619 | 620 | 623 | 626 | 628 | 630 | 632 | 633 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
hour | |||||||||||||||||||||
5 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
6 | 5.239433 | 4.956667 | 4.399933 | 5.106667 | 4.798300 | 2.839800 | 4.814800 | 8.125233 | 4.565933 | 4.392300 | ... | 4.063567 | 4.618067 | NaN | NaN | 4.084300 | 4.294333 | 5.081767 | 4.393567 | NaN | 4.984367 |
7 | 15.792925 | 12.676325 | 12.454250 | 15.955850 | 16.603350 | 11.173800 | 16.260775 | 16.862600 | 8.205025 | 17.796050 | ... | 18.206400 | 18.520050 | NaN | NaN | 12.868133 | 19.974975 | 14.635650 | 20.105725 | 9.960675 | 21.295725 |
8 | 31.014900 | 25.716900 | 25.566475 | 29.858975 | 33.727775 | 18.258967 | 32.383375 | 34.595275 | 12.375600 | 34.801025 | ... | 35.201325 | 35.601550 | 4.02680 | 4.2265 | 43.192900 | 37.974550 | 35.825333 | 37.598125 | 16.674375 | 37.867600 |
9 | 43.961825 | 38.884750 | 39.214675 | 41.552333 | 47.350775 | 28.385967 | 45.466800 | 42.528900 | 22.007500 | 47.553275 | ... | 48.639367 | 48.721900 | 4.04465 | 9.8240 | 48.129367 | 50.668475 | 18.799333 | 50.631975 | 45.409333 | NaN |
5 rows × 241 columns
bias = 100 * (1 - p_doy_hour['Pcalc'] / p_doy_hour['Pmeas'])
ax_p = bias.plot(xlim=[4, 20], ylim=[-50., 50.], legend=None, title='measured vs predicted', marker='.', linestyle=':')
ax_p.set_ylabel('bias [%]')
ax_p.plot(np.arange(4, 21), [mbe, mbe + rmse, mbe - rmse] * np.ones([17, 3]), 'r--', linewidth=2)
[<matplotlib.lines.Line2D at 0x267f5978>, <matplotlib.lines.Line2D at 0x27139908>, <matplotlib.lines.Line2D at 0x2712a320>]
mbe_hr = bias.mean(axis=1)
rmse_hr = np.sqrt(np.mean(bias ** 2, axis=1))
error_by_hour = pd.concat([mbe_hr, rmse_hr], axis=1)
error_by_hour.columns = ['MBE', 'RMSE']
error_by_hour.head()
MBE | RMSE | |
---|---|---|
hour | ||
5 | 6.415658 | 38.325467 |
6 | -12.644012 | 95.731759 |
7 | -2.429456 | 12.809025 |
8 | 0.330676 | 10.214374 |
9 | 2.159651 | 5.599081 |
ax_err_hr = error_by_hour['MBE'].plot(yerr=error_by_hour['RMSE'], ylim=[-40, 40], xlim=[4, 20],
title='MBE & RMSE of yearly energy by hour')
ax_err_hr.set_ylabel('error [%]')
ax_err_hr.plot(np.arange(4, 21), [mbe, mbe + rmse, mbe - rmse] * np.ones([17, 3]), 'r--', linewidth=2)
[<matplotlib.lines.Line2D at 0x27485940>, <matplotlib.lines.Line2D at 0x27e0c048>, <matplotlib.lines.Line2D at 0x27d53080>]
ax_p_tr = bias.T.plot(ylim=[-50., 50.], legend=None, title='measured vs predicted', marker='.', linestyle=':')
ax_p_tr.set_ylabel('bias [%]')
ax_p_tr.plot(np.arange(355, 751), [mbe, mbe + rmse, mbe - rmse] * np.ones([751-355, 3]), 'r--', linewidth=2)
[<matplotlib.lines.Line2D at 0x27d42c50>, <matplotlib.lines.Line2D at 0x2819bfd0>, <matplotlib.lines.Line2D at 0x2818a8d0>]
mbe_day = bias.mean(axis=0)
rmse_day = np.sqrt(np.mean(bias ** 2, axis=0))
error_by_day = pd.concat([mbe_day, rmse_day], axis=1)
error_by_day.columns = ['MBE', 'RMSE']
error_by_day.head()
MBE | RMSE | |
---|---|---|
doy | ||
227 | 2.214095 | 3.251163 |
228 | -0.552189 | 5.735184 |
229 | 2.980958 | 8.178492 |
230 | 1.186173 | 2.774053 |
231 | 4.314381 | 5.109505 |
ax_err_day = error_by_day['MBE'].plot(yerr=error_by_day['RMSE'], ylim=[-40, 40],
title='MBE & RMSE of yearly energy by day')
mbe_day = mbe * np.ones([error_by_day.index.size, 1])
mbe_rmse_day = np.concatenate([mbe_day, mbe_day + rmse, mbe_day - rmse], axis=1)
ax_err_day.plot(error_by_day.index, mbe_rmse_day, 'r--', linewidth=2)
ax_err_day.set_ylabel('error [%]')
<matplotlib.text.Text at 0x27e5ba20>
# from matplotlib.dates import DateFormatter,DayLocator
cdte_cocoa_resampled = cdte_cocoa.resample('H').mean()
bias_hr = 100 * (1 - power_hour['Pcalc'] / power_hour['Pmeas'])
cdte_cocoa_resampled.shape, bias_hr.shape
# ordinal_dates = [x.toordinal() for x in cdte_cocoa_resampled.index.to_datetime()]
fres = plt.figure(figsize=(16, 24))
# residual plots
residual_plots = ['Isc (A)', 'Voc (V)', 'Imp (A)', 'Vmp (V)', 'Pmp (W)', 'FF (%FF)',
'POA irradiance CMP22 pyranometer (W/m2)', 'PV module back surface temperature (degC)']
for n, rp in enumerate(residual_plots):
plt.subplot(4, 2, (n + 1))
plt.scatter(cdte_cocoa_resampled[rp], bias_hr,
s=cdte_cocoa_resampled.index.month * 3,
c=cdte_cocoa_resampled.index.hour)
plt.colorbar()
plt.ylim([-25, 25])
plt.xlabel(rp)
There is a bias between the predicted and measured DC energy. The bias appears to correlate most strongly with irradiance and therefore current and also power. There is a weak correlation with hour of the day, corresponding to large biases in morning and evening, but no apparent correlation with time of year. The average daily bias for the entire dataset (approx. one year) is 6.2% +/- 6.5% with uniform weight for all days.
There may be differences between the original characterization of the module and the actual field performance. Therefore recharacterizing the module SAPM coefficients may improve the prediction reducing the bias.
Before fitting the model, we filter spectral effects, since spectrum is not measured directly. For simplicity, we can also filter angular effects, even though it is measured already. If we only use pressure corrected airmass between 1.49 and 1.51 and angles of incidence less than 45 degrees that leaves 317 points from 38,011.
filter_idx = (1.49 < solpos_am_data['amp']) & (solpos_am_data['amp'] < 1.51) & (aoi < 45.0)
filter_data = cdte_cocoa[filter_idx]
filter_poa = poa[filter_idx]
filter_celltemp = celltemp[filter_idx]
filter_idx.sum()
41
The short circuit current is given by Eq. (1) in SAPM:
$$I_{sc} = I_{sc0} \frac{f_1 \left(E_b f_2 + E_{diff} \right)}{E_0} \left(1 + \alpha_{I_{sc}} \left(T_c - T_0 \right) \right)$$We can use least squares to solve for $I_{sc0}$ and $\alpha_{I_{sc}}$ if we write Eq. (1) as $Ax = B$ where:
$$B = I_{sc}$$$$A_1 = E_{eff} = f_1 \frac{ E_b f_2 + E_{diff} }{E_0}$$$$A_2 = E_{eff} \left(1 + \alpha_{I_{sc}} \left(T_c - T_0 \right) \right)$$$$x_1 = I_{sc0}$$$$x_1 = I_{sc0} \alpha_{I_{sc}}$$assuming that $f_1$ and $f_2$ are both one.
# solve for Isc0 and alphaIsc
filter_eff = (filter_poa['poa_direct'] + filter_poa['poa_diffuse']) / E0
A = pd.concat([filter_eff, filter_eff * (filter_celltemp - 25.0)], axis=1)
B = filter_data['Isc (A)']
x, res, rank, s = np.linalg.lstsq(A, B)
Isc0_new = x[0]
Aisc_new = x[1] / Isc0_new
print "new Isc0 = %g [A], alphaIsc = %g" % (Isc0_new, Aisc_new)
print "original values:\n\tIsc0 = %g [A], alphaIsc = %g" % tuple(sapm_coeff_renamed.loc[PVMODULE][['Isco', 'Aisc']])
print "norm of residuals: %g" % res
new Isc0 = 1.14954 [A], alphaIsc = -0.000964008 original values: Isc0 = 1.14486 [A], alphaIsc = 0.00051 norm of residuals: 0.0726505
Bres = pd.DataFrame({'Bpred': np.dot(A, sapm_coeff_renamed.loc[PVMODULE][['Isco', 'Aisc']]), 'B': B}, index=timestamps[filter_idx])
Bres.insert(0, 'bres', Bres['Bpred'] - Bres['B'])
print "original res norm: %g" % np.sum(Bres['bres']**2) # or np.linalg.norm(Bres['bres'])**2
original res norm: 0.0904157
from scipy import optimize
def calc_imp(x, eeff, tc, imp_):
imp0, c0, alpha_imp = x
return imp_ - imp0 * (c0 * eeff + (1 - c0) * eeff**2) * (1 + alpha_imp * (tc - 25.0))
params = (filter_eff, filter_celltemp, filter_data['Imp (A)'])
x = optimize.leastsq(calc_imp, x0=sapm_coeff_renamed.loc[PVMODULE][['Impo', 'C0', 'Aimp']], args=params)
Imp0_new, C0_new, Aimp_new = x[0]
print 'Imp0_new = %g [A], C0_new = %g, alphaImp = %g' % (Imp0_new, C0_new, Aimp_new)
Imp0_new = 0.955454 [A], C0_new = 1.15885, alphaImp = 0.00100305
sapm_coeff_renamed.loc[PVMODULE][['Impo', 'C0', 'Aimp']]
Impo 0.973822 C0 1.004510 Aimp 0.000297 Name: CdTe75669, dtype: float64
print 'new res = %g' % np.sum(calc_imp(x[0], *params)**2)
print 'old res = %g' % np.sum(calc_imp(sapm_coeff_renamed.loc[PVMODULE][['Impo', 'C0', 'Aimp']], *params)**2)
new res = 0.0390628 old res = 0.0537328
from scipy import constants
ns = sapm_coeff_renamed.loc[PVMODULE]['Cells_in_Series']
kb = constants.Boltzmann
qe = constants.elementary_charge
vt0 = kb * (25.0 + 273.15) / qe
A = np.concatenate([
np.ones((filter_idx.sum(),1)),
(ns * kb * (filter_celltemp + 273.15) / qe * np.log(filter_eff)).reshape(-1,1),
(filter_celltemp - 25.0).reshape(-1,1)
], axis=1)
x, res, rank, s = np.linalg.lstsq(A, filter_data['Voc (V)'])
Voco_new = x[0]
N_new = x[1]
Bvoco_new = x[2]
print 'Voco_new = %g [V], ideality_new = %g, bVoco = %g [V/C]' % (Voco_new, N_new, Bvoco_new)
print sapm_coeff_renamed.loc[PVMODULE][['Voco', 'N', 'Bvoco']]
Voco_new = 88.3906 [V], ideality_new = 1.11782, bVoco = -0.199389 [V/C] Voco 89.152700 N 1.375500 Bvoco -0.214484 Name: CdTe75669, dtype: float64
# calculate norm of residuals
print 'new res norm = %g' % res
Bres = pd.DataFrame(
{'Bpred': np.dot(A, sapm_coeff_renamed.loc[PVMODULE][['Voco', 'N', 'Bvoco']]), 'B': filter_data['Voc (V)']},
index=timestamps[filter_idx]
)
Bres.insert(0, 'bres', Bres['Bpred'] - Bres['B'])
print "original res norm: %g" % np.sum(Bres['bres']**2) # or np.linalg.norm(Bres['bres'])**2
new res norm = 4.49897 original res norm: 13.5649
def calc_vmp(x, eeff, tc, vmp):
vmp0, c2, c3, bvmpo = x
vt = N_new * kb * (tc + 273.15) / qe
return vmp - vmp0 - c2 * ns * vt * np.log(eeff) - c3 * ns * (vt * np.log(eeff))**2 - bvmpo * (tc - 25.0)
params = (filter_eff, filter_celltemp, filter_data['Vmp (V)'])
x = optimize.leastsq(calc_vmp, x0=sapm_coeff_renamed.loc[PVMODULE][['Vmpo', 'C2', 'C3', 'Bvmpo']], args=params)
Vmp0_new, C2_new, C3_new, Bvmpo_new = x[0]
print 'Vmp0_new = %g [V], C2_new = %g, C2_new = %g [1/V], betaVmp0 = %g' % (Vmp0_new, C2_new, C3_new, Bvmpo_new)
Vmp0_new = 65.0892 [V], C2_new = -0.960607, C2_new = -19.3955 [1/V], betaVmp0 = -0.148677
sapm_coeff_renamed.loc[PVMODULE][['Vmpo', 'C2', 'C3', 'Bvmpo']]
Vmpo 64.868700 C2 -0.858319 C3 -16.587300 Bvmpo -0.166692 Name: CdTe75669, dtype: float64
print 'new res = %g' % np.sum(calc_vmp(x[0], *params)**2)
print 'old res = %g' % np.sum(calc_vmp(sapm_coeff_renamed.loc[PVMODULE][['Vmpo', 'C2', 'C3', 'Bvmpo']], *params)**2)
new res = 4.46902 old res = 14.372
sapm_coeff_new = sapm_coeff_renamed.copy()
sapm_coeff_new['Isco'] = Isc0_new
sapm_coeff_new['Aisc'] = Aisc_new
sapm_coeff_new['Impo'] = Imp0_new
sapm_coeff_new['Aimp'] = Aimp_new
sapm_coeff_new['C0'] = C0_new
sapm_coeff_new['C1'] = 1 - C0_new
sapm_coeff_new['Voco'] = Voco_new
sapm_coeff_new['Bvoco'] = Bvoco_new
sapm_coeff_new['N'] = N_new
sapm_coeff_new['Vmpo'] = Vmp0_new
sapm_coeff_new['C2'] = C2_new
sapm_coeff_new['C3'] = C3_new
sapm_coeff_new['Bvmpo'] = Bvmpo_new
pd.concat([sapm_coeff_new.loc[PVMODULE],sapm_coeff_renamed.loc[PVMODULE]], axis=1)
CdTe75669 | CdTe75669 | |
---|---|---|
Vintage | NaN | NaN |
Module Area [m^2] | 7.200000e-01 | 7.200000e-01 |
Material | NaN | NaN |
Cells_in_Series | 1.160000e+02 | 1.160000e+02 |
Parallel C-S | 1.000000e+00 | 1.000000e+00 |
Isco | 1.149540e+00 | 1.144860e+00 |
Voco | 8.839062e+01 | 8.915270e+01 |
Impo | 9.554540e-01 | 9.738220e-01 |
Vmpo | 6.508919e+01 | 6.486870e+01 |
Aisc | -9.640083e-04 | 5.100000e-04 |
Aimp | 1.003045e-03 | 2.970000e-04 |
C0 | 1.158851e+00 | 1.004510e+00 |
C1 | -1.588510e-01 | -4.508150e-03 |
Bvoco | -1.993886e-01 | -2.144840e-01 |
Mbvoc | 0.000000e+00 | 0.000000e+00 |
Bvmpo | -1.486768e-01 | -1.666920e-01 |
Mbvmp | 0.000000e+00 | 0.000000e+00 |
N | 1.117819e+00 | 1.375500e+00 |
C2 | -9.606069e-01 | -8.583190e-01 |
C3 | -1.939547e+01 | -1.658730e+01 |
A0 | 9.875896e-01 | 9.875896e-01 |
A1 | 1.467286e-02 | 1.467286e-02 |
A2 | -3.924308e-03 | -3.924308e-03 |
A3 | 9.745336e-05 | 9.745336e-05 |
A4 | -2.810669e-06 | -2.810669e-06 |
B0 | 1.000000e+00 | 1.000000e+00 |
B1 | -3.159504e-03 | -3.159504e-03 |
B2 | 4.144566e-04 | 4.144566e-04 |
B3 | -1.740940e-05 | -1.740940e-05 |
B4 | 2.870862e-07 | 2.870862e-07 |
B5 | -1.748383e-09 | -1.748383e-09 |
d(Tc) | 3.000000e+00 | 3.000000e+00 |
FD | 1.000000e+00 | 1.000000e+00 |
a | -3.262124e+00 | -3.262124e+00 |
b | -9.152990e-02 | -9.152990e-02 |
C4 | 1.002430e+00 | 1.002430e+00 |
C5 | -2.427440e-03 | -2.427440e-03 |
IXO | 1.102710e+00 | 1.102710e+00 |
IXXO | 6.519600e-01 | 6.519600e-01 |
C6 | 1.120490e+00 | 1.120490e+00 |
C7 | -1.204920e-01 | -1.204920e-01 |
Data Source | 1.000000e+00 | 1.000000e+00 |
new_results = sapm(eff, celltemp, sapm_coeff_new.loc[PVMODULE])
new_results.head()
i_sc | i_mp | v_oc | v_mp | p_mp | i_x | i_xx | |
---|---|---|---|---|---|---|---|
Time Stamp (local standard time) yyyy-mm-ddThh:mm:ss | |||||||
2012-08-14 06:15:53 | 0.064357 | 0.060520 | 80.683240 | 60.661257 | 3.671235 | 0.061877 | 0.040654 |
2012-08-14 06:30:53 | 0.093769 | 0.088129 | 81.563542 | 62.767062 | 5.531610 | 0.090150 | 0.059069 |
2012-08-14 06:45:53 | 0.133596 | 0.125507 | 82.225349 | 64.194401 | 8.056819 | 0.128429 | 0.083842 |
2012-08-14 07:00:53 | 0.190220 | 0.178612 | 82.677516 | 65.055963 | 11.619781 | 0.182841 | 0.118733 |
2012-08-14 07:15:53 | 0.253744 | 0.237919 | 82.936272 | 65.374833 | 15.553884 | 0.243868 | 0.157415 |
power_new = (new_results['p_mp'] * soiling).resample('5T').mean().groupby(pd.TimeGrouper("D")).sum().dropna() * 5.0 / 60.0
power_new.head()
Time Stamp (local standard time) yyyy-mm-ddThh:mm:ss 2012-08-14 126.702260 2012-08-15 94.094709 2012-08-16 90.905101 2012-08-17 137.234875 2012-08-18 150.365061 dtype: float64
bias_new = 100 * (1 - power_new / power_day['Pmeas'])
mbe_new = bias_new.mean()
rmse_new = np.sqrt(np.mean(bias_new**2))
print 'New daily MBE = %g [%%], RMSE = %g [%%] for entire dataset (approx. one year)' % (mbe_new, rmse_new)
New daily MBE = -3.02989 [%], RMSE = 4.80016 [%] for entire dataset (approx. one year)